package com.mini.gateway.security;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import com.mini.common.core.constant.GlobalConstants;
import com.mini.common.core.constant.RedisConstants;
import com.mini.gateway.constant.GatewayConstants;
import com.mini.gateway.util.PathUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * 自定义鉴权管理器
 *
 * @author Zhou Feng
 */
@Slf4j
@Component
@ConfigurationProperties(prefix = "security")
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Setter
    private List<String> ignoreUrls = new ArrayList<>();

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String restPath = request.getMethodValue() + ":" + request.getURI().getPath();
        PathMatcher pathMatcher = new AntPathMatcher();
        // 对应跨域的预检请求直接放行 || 白名单路径直接放行
        if (request.getMethod() == HttpMethod.OPTIONS
                || PathUtils.isMatchAny(ignoreUrls, request.getURI().getPath())) {
            return Mono.just(new AuthorizationDecision(true));
        }

        // 从redis取 权限(路径url)->角色(集合)
        Map<Object, Object> permissionRolesMap = redisTemplate.opsForHash().entries(RedisConstants.URL_PERM_ROLES_KEY);
        // 拥有该权限的角色集合
        Set<String> rolesOfPermission = CollectionUtil.newHashSet();
        boolean isBlock = false;
        //将访问请求路径所需的角色存入 rolesOfPermission
        for (Map.Entry<Object, Object> pr : permissionRolesMap.entrySet()) {
            String permission = pr.getKey().toString();
            if (pathMatcher.match(permission, restPath)) {
                rolesOfPermission.addAll(Convert.toList(String.class, pr.getValue()));
                isBlock = true;
            }
        }
        //请求路径没有匹配，则直接放行
        if (!isBlock) {
            return Mono.just(new AuthorizationDecision(true));
        }
        // 判断用户中携带的角色是否有能通过权限拦截的角色
        Mono<AuthorizationDecision> authorizationDecisionMono = mono
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authority -> {
                    authority = authority.substring(GatewayConstants.AUTHORITY_PREFIX.length());
                    if (authority.startsWith(GlobalConstants.ROOT_ROLE_CODE)) {
                        // 如果是超级管理员则放行
                        return true;
                    }
                    // 用户角色中只要有一个满足则通过权限校验
                    return rolesOfPermission.contains(authority);
                })
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
        return authorizationDecisionMono;
    }
}
